/**
 *
 * David Witwer
 * Walter Lehner
 * Professor Rager
 * CS 241
 */

import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Random;

public class SearchBase {

//This version is for the pancake puzzle...

    private static final int PRINT_HOW_OFTEN = 100;
    private static final boolean A_Limit=true;
    private static final int N=10; //Puzzlesize if not initialized
    private static final boolean BUG_TESTING=false;
    public static int PuzzleSize= N;
    public static int increment = 1;//IDA incrementing. it might be necessary to adjust this depending on the heuristic
    public static int searchType =6;
    public static int depth_limit=2*N;
    public Random r=new Random();


    public static void main(String[] args)  {
          //First number is the search type, the second is the limit, the third is the puzzlesize
          //If two numbers are given, the first is the type, second is the puzzlesize and an extremely high limit is placed
          //If there are or one arguements, type of search and puzzle will be choosen at random.
          //Breadth First(1),
         //Bounded depth first(2), 
         //DFID(3), 
         //A*(4) 
         //IDA*(5)
         //
            
	     //Notes:
	                //IDA* search uses f(n) values for its limits. Thus place limits in an appropriate range
                        //I have included an int to control the incrementation of the IDA* search
            //Other systems that I might want to try...
                      //System.out.println("Choose your preferred search method: Breadth first search(1), Bounded Depth First Search(2),
                     // Depth First search with interative deepening(3), A*(4), or A* with Interative deepening(5)");
                      //String str = JOptionpane.showInputDialog(null,"Enter String");
                      //int searchType= Integer.parseInt(System.in.readln());
        try{
            searchType = Integer.parseInt(args[0]);
	    System.out.println("Searchtype: " + searchType);
            if((searchType==2)||(searchType==3)||(searchType==5)){

                depth_limit = Integer.parseInt(args[1]);
                PuzzleSize = Integer.parseInt(args[2]);

                if(searchType!=5){
                    System.out.println("Depth Limit: "+depth_limit);
                }
                else{
                    System.out.println("F Limit: " + depth_limit);
                }
            }
                System.out.println("Depth Limit: " +depth_limit);
            }
	    else{
         if(args.length==3){
           depth_limit = Integer.parseInt(args[1]);
           PuzzleSize=Integer.parseInt(args[2]);
         }
         //Assume the second variable is puzzle size.
         else if(args.length==2)(
           System.out.println("Taking second argument as puzzle size. ");
           PuzzleSize = Integer.parseInt(args[1]);
           depth_limit = 2*PuzzleSize;
         }
	    }
	    
        }
        catch(ArrayIndexOutOfBoundsException e){
        //If they put in one digit
          if(searchType==6);{
           searchType=4;
          }
         System.out.println("Unusable command-line arguments...\nArgument Syntax: [search type] [depth limit] [puzzle size]\nDefault variables now initialized:\n\tSearch type =  A*\n\tPuzzle size = "+PuzzleSize+"/n/depth Limit= " + depth_limit);
        }
        new SearchBase().process(depth_limit, searchType);
    }

    private void process(int depth_limit, int searchType) {
        
        int[] start = {3,4,2,0,1};
        int[] goal = {0,1,2,3,4};
        /*  Best performance:
        *    1. {3,4,2,0,1} --> {4,3,2,0,1}
        *    2. {4,3,2,0,1} --> {1,0,2,3,4}
        *    3. {1,0,2,3,4} --> {0,1,2,3,4}
        */

        //If bug testing, will use simple arrays defined above
        if(!BUG_TESTING){
            //If PuzzleSize was defined in the arguments...
            if(PuzzleSize!=0){
                start=new int[PuzzleSize];
                goal=new int[PuzzleSize];
                seed(start,goal);
            }

        }
        else{
            PuzzleSize=goal.length;//intialized here if testing
        }

        PassBoolean p = new PassBoolean();
        p.setSearch(searchType); //PassBoolean still has the kind of interaction

        int NEXT_DEPTH = depth_limit + 1;
        //Does DFID and IDA* 
        if(searchType==3){
             DFIDSearch(p , depth_limit, new PancakePuzzle(start, goal));
        }
        if(searchType==5){
             IDASearch(p , depth_limit, new PancakePuzzle(start, goal)); 
        }
	//A*
        if(searchType==4){
            NEXT_DEPTH = ASearch(p , depth_limit, new PancakePuzzle(start, goal));
        }
	//Breadthfirst
        if(searchType==1){
            NEXT_DEPTH = bestFirstSearch(p , depth_limit, new PancakePuzzle(start, goal));
        }
//bounded depth first
       if(searchType==2){
           NEXT_DEPTH = vectorSearch(p, depth_limit, new PancakePuzzle(start, goal));
       }

        System.out.println("Search stopped at: "+p.getValue());
        
        if(p.getValue()==false){
            System.out.println("Suggested next depth is "+NEXT_DEPTH);
        }

        else{
            System.out.println("The goal was found: "+p.getValue());
        }


        }
    private void seed(int[] start, int[] goal){
        int rand;
        int temp;
        for(int i=0; i<PuzzleSize; i++){
            goal[i]=i;
            start[i]=i;
        }
        for(int i=PuzzleSize; i>0; i--){
            temp = start[PuzzleSize-i];
            rand = r.nextInt(i);
            start[PuzzleSize-i]=start[rand];
            start[rand]=temp;
        }
    }

        public int search(PassBoolean done, int limit, StateSpace ssp, SearchList open) {

                System.out.println("Starting search with limit "+ limit);
                open.add(new State(ssp.getStart(),ssp.getGoal(), done.getSearch()));
                int count = 0;

                while (!done.getValue()) {

                        if (open.size()==0) {
                                System.out.println("open list empty at "+count);
                                break;
                        }

                        State current =  open.remove();
                        count++;
                        if (count % PRINT_HOW_OFTEN == 0) {
                                System.out.println("Search limit "+limit+" at Node # "+
                                                count+" Open list length:"+open.size()+" Current Node "+
                                                current.toString()+"  Depth: "+current.getDepth());
                        }
                        if (ssp.isGoal(current.getRep())) {
                                done.set(true);
                                System.out.println("--------\nFlip #"+count+" found goal at "+current.toString()+" at depth "+current.getDepth());
                                current.printPath();
                                break;
                        }
                           //This should now handle the search types appropriately
                           int toFar;
                          if(done.getSearch()==5){
                            toFar=current.getF();
                          }
                          if(done.getSearch()==4){
                            toFar=current.getDepth();
                            if(A_Limit){
                              limit=toFar+1;
                            }
                           }
                           else{
                             toFar=current.getDepth();
                            }
                           
                        if (toFar < limit) {
                                Vector<State> kids = ssp.getKids(current);
                                for (State v : kids) {
                                        if (!current.getPath().contains(v)){
                                            open.add(v);
                                           }
                                    }
                        }
		}
                return limit+1;
             }


        public int vectorSearch(PassBoolean done, int limit, StateSpace ssp) {
            //      System.out.println("Depth First search");

                return search(done,limit,ssp,new VectorasList());
        }

       //breadthfirst
        public int bestFirstSearch(PassBoolean done, int limit, StateSpace ssp) {

                return search(done,limit,ssp,new SearchQueue());
        }
        //A*
        public int ASearch(PassBoolean done, int limit, StateSpace ssp) {
	    //
                return search(done,limit,ssp,new PQasList());
        }
        //DFID
        public void DFIDSearch(PassBoolean done, int limit, StateSpace ssp) {
            //      System.out.println("Depth first interative deepening search");
                int l=search(done,limit,ssp,new VectorasList());
                while(!done.getValue()){
                   l=  search(done,l,ssp,new VectorasList());
                }
        }
        //IDA*
       public void IDASearch(PassBoolean done, int limit, StateSpace ssp) {
            //      System.out.println("Interative Deepening A*");
                int l=search(done,limit,ssp,new VectorasList());
                while(!done.getValue()){
                    search(done,l,ssp,new VectorasList());
                    l+= increment;
                }
        }
}